package cs236369.webserver;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;

//Listens to the incoming connections and upon the arrival
//puts them (the sockets) in the socket queue.
class ConnectionListener extends Thread {

	private final ServerSocket serversocket;
	private final HttpParams params;
	private final Queue<Socket> socketQueue;

	public ConnectionListener(int port, HttpParams _params,
			Queue<Socket> _socketQueue) throws IOException {
		this.socketQueue = _socketQueue;
		this.serversocket = new ServerSocket(port);
		this.params = _params;
	}

	public void run() {
		// Go on accepting incoming connections.
		Log.out("|=======================( Listening )=======================|");
		while (true) {
			Socket socket = null;
			try {
				socket = this.serversocket.accept();
			} catch (IOException e) {
				Log.err("ConnectionListener: Cannot accept connection");
			}
			/*
			 * 
			 * In case the socketQueue is full: 1) Reply with 503 2) Close
			 * client connection 3) Continue listening (Inside an infinite
			 * loop).
			 */
			if (socketQueue.full()) {
				DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
				try {
					conn.bind(socket, this.params);
					conn.setSocketTimeout(7000);
				} catch (IOException e) {
					Log.err("ConnectionListener: Cannnot bind connection");
				}
				HttpContext context = new BasicHttpContext(null);
				try {
					/*
					 * Create the response.
					 */
					HttpResponse response = new DefaultHttpResponseFactory()
							.newHttpResponse(HttpVersion.HTTP_1_1,
									HttpStatus.SC_SERVICE_UNAVAILABLE, context);
					//response.setStatusCode(HttpStatus.SC_SERVICE_UNAVAILABLE);
					EntityTemplate body = new EntityTemplate(
							new ContentProducer() {
								@Override
								public void writeTo(final OutputStream outstream)
										throws IOException {
									OutputStreamWriter writer = new OutputStreamWriter(
											outstream, "UTF-8");
									writer.write("<html><body><h1>");
									writer.write("HTTP Error 503 - Service unavailable ");
									writer.write("</h1></body></html>");
									writer.flush();
								}
							});
					body.setContentType("text/html; charset=UTF-8");
					response.setEntity(body);
					/*
					 * Send the response back to the client.
					 */
					BasicHttpProcessor proc = new BasicHttpProcessor();
					proc.process(response, context);
					conn.sendResponseHeader(response);
					conn.sendResponseEntity(response);
					conn.flush();
					conn.close();

				} catch (ConnectionClosedException ex) {
					Log.err("ConnectionListener: Client closed connection");
				} catch (IOException ex) {
					Log.err("ConnectionListener: IO problem");
				} catch (HttpException ex) {
					Log.err("ConnectionListener: Unrecoverable HTTP protocol violation: "+ex.getMessage());
				} finally {
					try {
						conn.shutdown();
					} catch (IOException ignore) {
					}
				}
			} else {
				/*
				 * Otherwise, enqueue the socket into the queue.
				 */
				synchronized (socketQueue) {
					Log.out("ConnectionListener: Enqueing socket to socketQueue");
					if (!socketQueue.full()) {
						socketQueue.enqueue(socket);
						socketQueue.notifyAll();
					}
				}
			}
		}
	}
}
